#!/usr/bin/python
# -*- coding: utf-8 -*-


# cmap2owl -- Helper application to convert from concept maps to OWL ontologies
# Copyright (c) 2008-2013  Rodrigo Rizzi Starr
#  
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#  
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#  
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.


'''
A class that draws cmaps

@author: Rodrigo Rizzi Starr
@copyright: Copyright © 2008-2013 Rodrigo Rizzi Starr
@license: MIT License
@contact: rodrigo.starr@gmail.com
'''


from __future__ import division
import sys
from math import sin, cos, pi, atan2

# GTK+ imports
import pygtk
if not sys.platform == 'win32':
    pygtk.require('2.0')
try:
    import gtk
    from gtk import gdk
    import gtk.gdk
    import cairo
    import pango
    import pangocairo
except:
    sys.exit(1)

# Project imports
from log import log
log.debug("Loaded...")


class Canvas(gtk.DrawingArea):
    """
      Has special utilities to draw a cmap on the screen and highlight some of
      its nodes.
    """
    
    # Draw in response to an expose-event
    __gsignals__ = { "expose-event": "override" }
    
    def __init__(self):
        gtk.DrawingArea.__init__(self)
        self.cr = None
        
    def redraw_canvas(self):
        "Force a redraw of everything"
        if self.window:
            alloc = self.get_allocation()
            rect = gdk.Rectangle(alloc.x, alloc.y, alloc.width, alloc.height)
            self.window.invalidate_rect(rect, True)
            self.window.process_updates(True)

    # Handle the expose-event by drawing
    def do_expose_event(self, event):
        "This is called when the event expose-event is emitted"

        # Create the cairo context
        self.cr = self.window.cairo_create()
        self.cr.set_antialias(cairo.ANTIALIAS_SUBPIXEL)
        fo = self.cr.get_font_options()
        fo.set_antialias(cairo.ANTIALIAS_SUBPIXEL)
        
        # Restrict Cairo to the exposed area; avoid extra work
        self.cr.rectangle(event.area.x, event.area.y,
                event.area.width, event.area.height)
        self.cr.clip()
        self.draw()

    def drawBackground(self):
        "Draws the lowest part of the canvas"
        
        self.cr.set_source_rgb(1.0, 1.0, 1.0)
        width, height = self.window.get_size()
        self.cr.rectangle(0, 0, width, height)
        self.cr.fill()
        
    def drawBox(self, x, y, width, height, color, bbox = False):
        "Draws a rectangle with a label inside"
        
        cr = self.cr
        
        # draw a rectangle
        cr.set_source_rgb(*color)
        cr.set_line_width(.5)
        self.drawRoundBox(x, y, width, height)
        if bbox:
            cr.fill_preserve()
            cr.set_source_rgb(0, 0, 0)
            cr.stroke()
        else:
            cr.fill()
        
    def drawLabel(self, x, y, width, height, label):
        "Draws a label inside the box"
        
        cr = self.cr
        cr.set_source_rgb(0.0, 0.0, 0.0)
        
        pangocr = pangocairo.CairoContext(self.cr)
        layout = pangocr.create_layout()
        layout.set_font_description(pango.FontDescription("Arial, 10"))
        layout.set_alignment(pango.ALIGN_CENTER)
        layout.set_text(label)
        ref_width, ref_height = layout.get_size()
        ref_width /= pango.SCALE
        ref_height /= pango.SCALE
        cr.move_to(x  + (width-ref_width)/2, y + (height-ref_height) / 2 - 2)
        
        pangocr.show_layout(layout)
#        cr.show_text(label)
        
    def drawRoundBox(self, x, y, width, height, percent = .6):
        """Draws a box with round corners. The round corners will have as radius
            percent/2 of the smallest side"""
        radius = percent*height/2 if height <= width else percent*width/2
        cr = self.cr
        
        x1 = x + width
        y1 = y + height
        
        cr.move_to(x+radius, y)
        
        cr.rel_line_to(width-2*radius, 0)
        cr.arc(x1-radius, y+radius, radius, -pi/2, 0)
        
        cr.rel_line_to(0, height-2*radius)
        cr.arc(x1-radius, y1-radius, radius, 0, pi/2)
        
        cr.rel_line_to(-width+2*radius, 0)
        cr.arc(x+radius, y1-radius, radius, pi/2, pi)
             
        cr.rel_line_to(0, -height+2*radius)
        cr.arc(x+radius, y+radius, radius, pi, 3*pi/2)

    def drawArrow(self, x, y, angle = 0, size = 5):
        """Angle is in radians """
        
        cr = self.cr
        cosa = cos(-angle)*size
        sina = sin(-angle)*size
        
        cr.move_to(x, y)
        cr.line_to(x-2*cosa-sina, y+2*sina-cosa)
        cr.line_to(x-2*cosa+sina, y+2*sina+cosa)
        cr.line_to(x, y)
        cr.fill()
        cr.stroke()
    
    def drawLine(self, x1, y1, x2, y2,
                 arrow = False,
                 color = (0,0,0,1),
                 lineWidth = 1.0):
        """Draws a line from (x1,y1) to (x2,y2). If arrow, the tip will have an
          arrow"""
          
        cr = self.cr
        
        # Set Cairo's state
        cr.set_source_rgba(*color)
        cr.set_line_width(lineWidth)
        
        # Draws the line in itself
        cr.move_to(x1, y1)
        cr.line_to(x2, y2)
        cr.stroke()
        
        # And the arrow
        if arrow:
            angle = atan2(y2-y1, x2-x1)
            self.drawArrow(x2, y2, angle)

    def drawCurve(self, x1, y1, x2, y2, x3, y3, x4, y4,
                 arrow = False,
                 color = (0,0,0,1),
                 lineWidth = 1.0):
        """Draws a bézier from (x1,y1) to (x2,y2). If arrow, the tip will have
        an arrow"""
          
        cr = self.cr
        
        # Set Cairo's state
        cr.set_source_rgba(*color)
        cr.set_line_width(lineWidth)
        
        # Draws the line in itself
        cr.move_to(x1, y1)
        cr.curve_to(x2, y2, x3, y3, x4, y4)
        cr.stroke()
        
        # And the arrow
        if arrow:
            angle = atan2(y4-y3, x4-x3)
            self.drawArrow(x4, y4, angle)

